package com.aos.erp.common.redis.impl;

import com.aos.erp.common.lock.AxLock;
import com.aos.erp.common.lock.AxLockService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisConnectionUtils;
import org.springframework.data.redis.core.StringRedisTemplate;

import javax.annotation.Resource;
import java.util.List;
import java.util.UUID;

/**
 * redis 分布式锁实现
 */
@Slf4j
public class RedisLockServiceImpl implements AxLockService {

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    public String lock(String lockName) {
        return lockWithTimeout(lockName, 3000, 3000, 30);
    }

    public String lockWithTimeout(String lockName, AxLock lock) {
        return lockWithTimeout(lockName, lock.expire(), lock.timeout(), lock.unit());
    }

    /**
     * 获取锁
     *
     * @param lockName
     * @param expire
     * @param unit
     * @return
     */
    public String lockWithTimeout(String lockName, int expire, int timeout, int unit) {
        String retIdentifier = null;
        RedisConnectionFactory connectionFactory = stringRedisTemplate.getConnectionFactory();
        RedisConnection redisConnection = connectionFactory.getConnection();
        // 获取连接
        // 随机生成一个value
        String identifier = UUID.randomUUID().toString();
        // 锁名，即key值
        String lockKey = "lock:" + lockName;

        log.debug("加锁申请：" + lockKey + " " + identifier);
        // 超时时间，上锁后超过此时间则自动释放锁
        int lockExpire = (int) (expire / 1000);
        // 获取锁的超时时间，超过这个时间则放弃获取锁
        long end = System.currentTimeMillis() + timeout;
        while (System.currentTimeMillis() < end) {
            if (redisConnection.setNX(lockKey.getBytes(), identifier.getBytes())) {
                redisConnection.expire(lockKey.getBytes(), lockExpire);
                // 返回value值，用于释放锁时间确认
                retIdentifier = identifier;
                RedisConnectionUtils.releaseConnection(redisConnection, connectionFactory);

                log.info("加锁成功：" + lockKey + " " + identifier);
                return retIdentifier;
            }

           /*
           // 返回-1代表key没有设置超时时间，为key设置一个超时时间
            if (redisConnection.ttl(lockKey.getBytes()) == -1) {
                redisConnection.expire(lockKey.getBytes(), lockExpire);
            }*/

            // key 属于无效期，过期了，删除掉，并重试试
            if (redisConnection.ttl(lockKey.getBytes()) < 0) {
                log.error("发现过期锁" + lockKey + "，执行删除");
                redisConnection.del(lockKey.getBytes());
                continue;
            }

            try {
                Thread.sleep(unit);
                log.debug("尝试加锁：" + lockKey + " " + identifier);
            } catch (InterruptedException e) {
                log.error("获取到分布式锁：线程中断！");
                Thread.currentThread().interrupt();
            }
        }
        RedisConnectionUtils.releaseConnection(redisConnection, connectionFactory);

        log.info("加锁失败：" + lockKey + " " + identifier);
        return retIdentifier;
    }

    /**
     * 释放锁
     *
     * @param lockName   锁的key
     * @param identifier 释放锁的标识
     * @return
     */
    public boolean releaseLock(String lockName, String identifier) {
        if (identifier == null || "".equals(identifier)) {
            return false;
        }
        RedisConnectionFactory connectionFactory = stringRedisTemplate.getConnectionFactory();
        RedisConnection redisConnection = connectionFactory.getConnection();
        String lockKey = "lock:" + lockName;
        boolean releaseFlag = false;
        while (true) {
            try {
                // 监视lock，准备开始事务
                redisConnection.watch(lockKey.getBytes());
                // 通过前面返回的value值判断是不是该锁，若是该锁，则删除，释放锁
                byte[] valueBytes = redisConnection.get(lockKey.getBytes());
                if (valueBytes == null) {
                    redisConnection.unwatch();
                    releaseFlag = false;
                    break;
                }
                String identifierValue = new String(valueBytes);
                if (identifier.equals(identifierValue)) {
                    redisConnection.multi();
                    redisConnection.del(lockKey.getBytes());
                    List<Object> results = redisConnection.exec();
                    if (results == null) {
                        continue;
                    }
                    releaseFlag = true;
                }
                redisConnection.unwatch();
                break;
            } catch (Exception e) {
                log.warn("释放锁异常", e);
                e.printStackTrace();
            }
        }
        RedisConnectionUtils.releaseConnection(redisConnection, connectionFactory);
        return releaseFlag;
    }

}